﻿using System;
using System.Threading;
using Cfg.Exceptions;

namespace Cfg.Helpers
{
    internal class StringConverter
    {
        /// <summary>
        /// Converts string value to given type
        /// </summary>
        /// <param name="stringValue"></param>
        /// <param name="toType"></param>
        /// <returns></returns>
        public static object Convert(string stringValue, Type toType)
        {
            if (toType.IsArray)
            {
                return ConvertToArray(stringValue, toType);
            }

            if (toType.IsGenericType && toType.GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                return ConvertToNullable(stringValue, toType);
            }

            if (toType.IsEnum)
            {
                return Enum.Parse(toType, stringValue);
            }
            /*
            if (to == typeof(ConfigValueBase))
            {
				
            }
            */
            var methodName = string.Format("To{0}", toType.Name);
            var convertMethod = typeof(Convert).GetMethod(methodName, new[] { typeof(string), typeof(IFormatProvider) });

            if (convertMethod == null)
            {
                string error = string.Format("Unable to convert string '{0}' to type {1}. Add interface IParseable to type {1} or create add custom parser for type {1} to AppSettings.Settings", stringValue, toType);
                throw new InvalidFormatException(error);
            }

            var ci = AppSettings.Settings.Culture ?? Thread.CurrentThread.CurrentCulture;
            return convertMethod.Invoke(null, new object[] { stringValue, ci });
        }

        /// <summary>
        /// Converts string value to nullable object
        /// </summary>
        /// <param name="stringValue"></param>
        /// <param name="toType"></param>
        /// <returns></returns>
        private static object ConvertToNullable(string stringValue, Type toType)
        {
            var elementType = Nullable.GetUnderlyingType(toType);

            if (string.IsNullOrEmpty(stringValue))
            {
                return null;
            }

            return Convert(stringValue, elementType);
        }

        /// <summary>
        /// Converts string value to array
        /// </summary>
        /// <param name="stringValue"></param>
        /// <param name="to"></param>
        /// <returns></returns>
        private static object ConvertToArray(string stringValue, Type to)
        {
            // todo - array of arrays
            var elementType = to.GetElementType();
            var split = stringValue.Split(AppSettings.Settings.ArrayDelimiters);

            var array = Array.CreateInstance(elementType, split.Length);
            for (var i = 0; i < split.Length; ++i)
            {
                var itemValue = Convert(split[i].Trim(), elementType);
                array.SetValue(itemValue, i);
            }

            return array;
        }
    }
}
